package ex1;

/**
 * Worker extends Thread.
 * Implements the basic thread which works on a specific subset of the game board.
 * Each worker has its own set of cells, and no shared memory exists.
 * When a cell recieves an "evolve" message, it contacts the relvant cells by itself, regardless of which
 * thread they reside in.
 * When the working thread discovers it has nothing further to do at the moment, it goes out to wait() until
 * a relvant neighbour cell is changed - which leads to a notification.
 */
public class Worker extends Thread {
	
	ICell cells[][];
	int maxGen;
	Master father;
	
	/**
	 * Main function of the thread.
	 * Tries to make all cells evolve while there are cells not on their maximal generation.
	 * Algorithm:
	 * 1. Evolve all cells.
	 * 2. If no cells could be evolved:
	 * 2.1. Accquire self-worker's lock
	 * 2.2. See if there are any cells that can be evolved (<b>DON'T</b> evolve even if found)
	 * 2.3. If no such cells found, wait(). - otherwise go to 1.
	 * 3. Notify Master (ParallelGameOfLife) upon completion.  
	 */
	public void run () {
		while (!done()) {
			if (!evolve())
				synchronized(this) { 
					if (!canEvolve())
						try { wait(); } 
						catch (InterruptedException e) { e.printStackTrace(); }
				}
		}

		father.notifyDone();
	}
	
	/**
	 * Checks if there are any cells that can be evolved and have not yet reached their maximal generation.
	 * 
	 * @return true if there's any cell that can be evolved, false otherwise.
	 */
	protected boolean canEvolve () {
		for (ICell[] cline : cells)
			for (ICell c : cline)
				if (c.canEvolve() && !c.done())
					return true;
		return false;
	}
	
	/**
	 * Checks if all cells have reached their maximal generation.
	 * 
	 * @return true if all cells are done, false otherwise.
	 */
	protected boolean done () {
		for (ICell[] cline : cells)
			for (ICell c : cline)
				if (!c.done())
					return false;
		return true;
	}
	
	/**
	 * Attempts to evolve all cells.
	 * 
	 * @return true if there was at least one cell that evolved, false otherwise.
	 */
	protected boolean evolve () {
		boolean evolved = false;
		
		// top row:
		for (int i = 0 ; i < cells[0].length ; i++)
			evolved = cells[0][i].evolve() || evolved;
		
		// bottom row: (but we don't wanna do the first row again if there is only one)
		if (cells.length >= 2)
			for (int i = 0 ; i < cells[cells.length-1].length ; i++)
				evolved = cells[cells.length-1][i].evolve() || evolved;
		
		if (cells.length >= 3) {
			// left column:
			for (int i = 1 ; i < cells.length-1 ; i++)
				evolved = cells[i][0].evolve() || evolved;
			// right column: (we don't wanna do the column again if there is only one)
			if (cells[0].length >= 2)
				for (int i = 1 ; i < cells.length-1 ; i++)
					evolved = cells[i][cells[i].length-1].evolve() || evolved;
		}
		
		for (int i = 1 ; i < cells.length-1 ; i++)
			for (int j = 1 ; j < cells[i].length-1 ; j++)
				evolved = cells[i][j].evolve() || evolved;
		
		return evolved;
	}
	
	/**
	 * Worker constructor.
	 * 
	 * @param field initial game field.
	 * @param row starting row in responsibility of the new worker.
	 * @param col starting col in responsibility of the new worker.
	 * @param width the amount of horizontal cells (starting from 'col') in responsibility of the new worker.
	 * @param height the amount of vertical cells (starting from 'row') in responsibility of the new worker.
	 * @param generations the desired maximal generation.
	 * @param father The master of the worker. [ParallelGameOfLife, obviously]
	 */
	public Worker (boolean[][] field, int row, int col, int width, int height, int generations, Master father) {
		maxGen = generations;
		this.father = father;
		cells = new ICell[height][];
		for (int i = 0 ; i<cells.length ; i++) {
			cells[i] = new ICell[width];
			for (int j = 0 ; j<cells[i].length ; j++) {
				if ((i == 0 && row != 0) || (i == cells.length-1 && row+i != field.length-1) ||
						(j == 0 && col != 0) || (j == cells[i].length-1 && col+j != field[0].length-1))
					cells[i][j] = new SynchedCell(field[row+i][col+j], generations, this);
				else
					cells[i][j] = new NormalCell(field[row+i][col+j], generations);
			}
		}
	}
	
	/**
	 * Used to gather the results achieved by the worker.
	 * Treats recieved row,col as initial coordinates to start setting results from.
	 * 
	 * @param field the field to set results on.
	 * @param row put results starting from this row.
	 * @param col puts results starting from this col.
	 */
	public void fillData (boolean field[][], int row, int col) {
		try {
		for (int i = 0 ; i<cells.length ; i++)
			for (int j = 0; j<cells[i].length ; j++)
				field[row+i][col+j] = cells[i][j].getState(maxGen);
		}
		catch (NotReady e) { throw new java.lang.Error(); }
	}

	/**
	 * Notifies the worker of its imidiate neighbours.
	 * Used to initialize all the neighbours of the contained cells.
	 * 
	 * @param tl top left worker of the current worker.
	 * @param tm top middle worker of the current worker.
	 * @param tr top right worker of the current worker.
	 * @param l left worker of the current worker.
	 * @param r right worker of the current worker.
	 * @param bl bottom left worker of the current worker.
	 * @param bm bottom middle worker of the current worker.
	 * @param br bottom right worker of the current worker.
	 */
	public void init (Worker tl, Worker tm, Worker tr, Worker l, Worker r, Worker bl, Worker bm, Worker br) {
		ICell dummy = new DummyCell();
		ICell ctl, ctm, ctr, cl, cr, cbl, cbm, cbr;
		for (int row = 0 ; row < cells.length ; row++)
			for (int col = 0 ; col < cells[row].length ; col++) {
				
				if (row == 0) {
					if (col == 0)
						ctl = tl == null ? dummy : tl.cells[tl.cells.length-1][tl.cells[tl.cells.length-1].length-1];
					else
						ctl = tm == null ? dummy : tm.cells[tm.cells.length-1][col-1];
				}
				else if (col == 0)
					ctl = l == null ? dummy : l.cells[row-1][l.cells[row-1].length-1];
				else
					ctl = cells[row-1][col-1];
				
				if (row == 0)
					ctm = tm == null ? dummy : tm.cells[tm.cells.length-1][col];
				else
					ctm = cells[row-1][col];
				
				if (row == 0) {
					if (col == cells[row].length-1)
						ctr = tr == null ? dummy : tr.cells[tr.cells.length-1][0];
					else
						ctr = tm == null ? dummy : tm.cells[tm.cells.length-1][col+1];
				}
				else if (col == cells[row].length-1)
					ctr = r == null ? dummy : r.cells[row-1][0];
				else
					ctr = cells[row-1][col+1];
				
				if (col == 0)
					cl = l == null ? dummy : l.cells[row][l.cells[row].length-1];
				else
					cl = cells[row][col-1];
				
				if (col == cells[row].length-1)
					cr = r == null ? dummy : r.cells[row][0];
				else
					cr = cells[row][col+1];
				
				if (col == 0) {
					if (row == cells.length-1)
						cbl = bl == null ? dummy : bl.cells[0][bl.cells[0].length-1];
					else
						cbl = l == null ? dummy : l.cells[row+1][l.cells[row+1].length-1];
				}
				else if (row == cells.length-1)
					cbl = bm == null ? dummy : bm.cells[0][col-1];
				else
					cbl = cells[row+1][col-1];
				
				if (row == cells.length-1)
					cbm = bm == null ? dummy : bm.cells[0][col];
				else
					cbm = cells[row+1][col];
				
				if (row == cells.length-1) {
					if (col == cells[row].length-1)
						cbr = br == null ? dummy : br.cells[0][0];
					else
						cbr = bm == null ? dummy : bm.cells[0][col+1];
				}
				else if (col == cells[row].length-1)
					cbr = r == null ? dummy : r.cells[row+1][0];
				else
					cbr = cells[row+1][col+1];
				
				cells[row][col].init(ctl, ctm, ctr, cl, cr, cbl, cbm, cbr);
			}
	}
	
}
